#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Modbus TCP服务器示例
基于pymodbus 3.11.2实现，支持多从站设备模拟
"""

import random
import time
import logging
import threading
from datetime import datetime

from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusServerContext, ModbusDeviceContext
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus import ModbusDeviceIdentification

# 日志配置
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# -------------------------------
# 配置参数
# -------------------------------
TCP_HOST = "127.0.0.1"
TCP_PORT = 5020

SLAVE_COUNT = 10          # 从站数量
REGISTER_COUNT = 20       # 保持寄存器数量
UPDATE_INTERVAL = 5       # 更新间隔（秒）

# -------------------------------
# 创建多从站数据上下文
# -------------------------------
def create_slave_context(register_count: int = REGISTER_COUNT):
    """创建单个从站的数据存储区"""
    return ModbusDeviceContext(
        di=ModbusSequentialDataBlock(0, [0] * 10),        # 离散输入
        co=ModbusSequentialDataBlock(0, [0] * 10),        # 线圈
        hr=ModbusSequentialDataBlock(0, [0] * register_count),  # 保持寄存器
        ir=ModbusSequentialDataBlock(0, [0] * 10),        # 输入寄存器
    )

# 创建多从站上下文
slave_contexts = {
    slave_id: create_slave_context() for slave_id in range(1, SLAVE_COUNT + 1)
}

# 服务器上下文（多从站模式）
context = ModbusServerContext(devices=slave_contexts, single=False)

# -------------------------------
# 创建设备标识
# -------------------------------
identity = ModbusDeviceIdentification()
identity.VendorName = 'IoT-Util'
identity.ProductCode = 'ModbusTCPSim'
identity.ProductName = 'Modbus TCP Multi-Slave Simulator'
identity.ModelName = 'TCP Model X'
identity.MajorMinorRevision = '1.0'

# -------------------------------
# 字符串转寄存器工具函数
# -------------------------------
def encode_string_to_registers(s: str, encoding: str = "ascii") -> list[int]:
    """把字符串编码为 Modbus 寄存器列表"""
    data = s.encode(encoding)
    if len(data) % 2:
        data += b'\x00'
    registers = []
    for i in range(0, len(data), 2):
        registers.append((data[i] << 8) + data[i + 1])
    return registers

# -------------------------------
# 更新寄存器线程函数
# -------------------------------
def update_registers(server_ref):
    """更新寄存器数据的线程函数"""
    logger.info(f"🔄 [{datetime.now().strftime('%H:%M:%S')}] 开始更新所有从站寄存器...")

    while getattr(server_ref, 'running', True):
        try:
            for slave_id, slave_context in slave_contexts.items():
                # 生成随机数值
                values = [random.randint(0, 65535) for _ in range(6)]
                slave_context.setValues(3, 0, values)

                # 字符串寄存器
                device_name = f"Device-{slave_id:03d}"
                str_regs = encode_string_to_registers(device_name)
                slave_context.setValues(3, 6, str_regs)

                logger.info(f"📊 从站{slave_id}: HR[0-5]={values} HR[6-{6+len(str_regs)-1}]={str_regs}")

            logger.info(f"✅ 完成更新 {SLAVE_COUNT} 个从站，间隔{UPDATE_INTERVAL}秒")
            time.sleep(UPDATE_INTERVAL)
        except Exception as e:
            logger.error(f"❌ 寄存器更新错误: {e}")
            time.sleep(UPDATE_INTERVAL)

# -------------------------------
# 启动 TCP 服务器
# -------------------------------
def start_tcp_server(host: str = TCP_HOST, port: int = TCP_PORT):
    """启动TCP服务器"""
    logger.info(f"🚀 启动Modbus TCP服务器: {host}:{port}")

    # 创建服务器引用用于控制停止
    server_ref = type('ServerRef', (), {'running': True})()

    # 启动数据更新线程
    update_thread = threading.Thread(target=update_registers, args=(server_ref,), daemon=True)
    update_thread.start()

    try:
        # 启动服务器（阻塞运行）
        logger.info(f"✅ TCP服务器已在 {host}:{port} 启动，等待连接...")
        StartTcpServer(
            context=context,
            identity=identity,
            address=(host, port)
        )
    except KeyboardInterrupt:
        logger.info("🛑 接收到停止信号")
    except Exception as e:
        logger.error(f"❌ 服务器运行错误: {e}")
    finally:
        # 停止服务器
        server_ref.running = False
        logger.info("🔌 TCP服务器已关闭")

# -------------------------------
# 主程序入口
# -------------------------------
if __name__ == "__main__":
    start_tcp_server()
